﻿using SyncCore.Sync.Utils;
using SyncDemo.Sync.Models;
using SyncDemo.Sync.States;
using SyncDemo.Sync.Utils;
using System.Net;
using System.Net.Sockets;

namespace SyncDemo.Sync
{
    public class Client : IClient
    {
        #region props

        IClientState IClient.State { get; set; }

        #endregion

        #region fields

        private Socket _clientSocket;
        private int _serverPort;
        private MemoryBuffer _buffer;

        private EndPoint _endPoint;
        private bool _isDisconnected;

        #endregion

        #region ctors

        public Client() : this(19999)
        {
        }

        public Client(int serverPort) : this(serverPort, 4096)
        {
        }

        public Client(int serverPort, int bufferSize)
        {
            _serverPort = serverPort;

            _buffer = new NetMemoryBuffer();

        }

        #endregion

        #region interface impls

        public virtual void ExecuteNewFrame()
        {

        }

        public AddressPortInfo StartListenHostBoardcast()
        {
            _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            _clientSocket.ReceiveBufferSize = _buffer.ReceiveBuffer.Length;
            _clientSocket.SendBufferSize = _buffer.SendBuffer.Length;
            //_clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
            //_clientSocket.SetSocketOption(SocketOptionLevel.Udp, SocketOptionName.Broadcast, true);
            var serverAddress = IPAddress.Any;
            var serverPort = _serverPort;
            var serverEndPoint = new IPEndPoint(IPAddress.Loopback, serverPort);
            _endPoint = serverEndPoint;

            var localEndPoint = new IPEndPoint(IPAddress.Any, 0);
            _clientSocket.Bind(localEndPoint);

            WaitLink();
            Console.WriteLine("linked");

            while (_isDisconnected == false)
            {
                try
                {
                    Console.WriteLine("start receive from");
                    _clientSocket.ReceiveFrom(_buffer.ReceiveBuffer, 0, _buffer.ReceiveBuffer.Length, SocketFlags.None, ref _endPoint);
                    Console.WriteLine("end receive from");
                    if (_buffer.TryDeserialize<RPCInfo>(out var rpcInfo))
                    {
                        ExecuteRPCInfo(rpcInfo);
                    }
                    else
                    {
                        Console.WriteLine("TryDeserialize failed");
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    Thread.Sleep(2000);
                }
            }

            Console.WriteLine("连接已断开");

            return null;
        }

        private void WaitLink()
        {
            while (_isDisconnected == false)
            {
                try
                {
                    SendHeartbeat();
                    _clientSocket.ReceiveFrom(_buffer.ReceiveBuffer, SocketFlags.None, ref _endPoint);

                    if (_buffer.TryDeserialize<RPCInfo>(out var rpcInfo))
                    {
                        if (rpcInfo.Type == RPCCommandEnum.Heartbeat)
                        {
                            Console.WriteLine("is heartbeat received");
                            return;
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    Thread.Sleep(1000);
                }

            }
        }

        private void ExecuteRPCInfo(RPCInfo info)
        {
            if (info == null) return;
            switch (info.Type)
            {
                // 信息回传。
                case RPCCommandEnum.Echo:
                    {
                        Echo(info);
                    }

                    break;
                case RPCCommandEnum.Heartbeat:
                    {
                        SendHeartbeat();
                    }

                    break;
                case RPCCommandEnum.Disconnect:
                    {
                        Disconnect();
                    }

                    break;
                default:
                    break;
            }
        }

        private void Echo(RPCInfo info)
        {
            var returnRpcInfo = new RPCInfo();
            returnRpcInfo.Type = RPCCommandEnum.Echo;
            var returnArgs = new List<string>();
            returnArgs.Add("sendByClient");
            returnArgs.AddRange(info.Arguments);
            returnRpcInfo.Arguments = returnArgs.ToArray();
            _buffer.Serialize(returnRpcInfo);
            _clientSocket.SendTo(_buffer.SendBuffer, 0, _buffer.SendBuffer.Length, SocketFlags.None, _endPoint);

            Console.WriteLine($"Echo {info}");
        }

        private void Disconnect()
        {
            var returnRpcInfo = new RPCInfo() { Arguments = ["client disconnect"] };
            returnRpcInfo.Type = RPCCommandEnum.Disconnect;
            _buffer.Serialize(returnRpcInfo);
            _clientSocket.SendTo(_buffer.SendBuffer, 0, _buffer.SendBuffer.Length, SocketFlags.None, _endPoint);

            Thread.Sleep(1000);
            _clientSocket.Disconnect(false);
            _isDisconnected = true;


            Console.WriteLine($"Disconnected");
        }

        private void SendHeartbeat()
        {
            var returnRpcInfo = new RPCInfo();
            returnRpcInfo.Type = RPCCommandEnum.Heartbeat;
            _buffer.Serialize(returnRpcInfo);
            _clientSocket.SendTo(_buffer.SendBuffer, 0, _buffer.SendBuffer.Length, SocketFlags.None, _endPoint);

            Console.WriteLine("Heartbeat");
        }

        #endregion

    }
}
